package gui.panels.jtree;

/*
 **  This is version II of DnDJTree. The first version allowed for what I 
 **  thought was a JDK oversight. However, we can set the cursor appropriately,
 **  relative to whether the current cursor location is a valid drop target.
 **
 **  If this is your first time reading the source code. Just ignore the above
 **  comment and ignore the "CHANGED" comments below. Otherwise, the 
 **  "CHANGED" comments will show where the code has changed.
 **
 **  Credit for finding this shortcoming in my code goes Laurent Hubert.
 **  Thanks Laurent.
 **
 **  Rob. [ rkenworthy@hotmail.com ]
 */



import gui.panels.jtree.filesystem.FileSystemCategoryInfo;
import gui.panels.jtree.filesystem.FileSystemCategoryNode;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.metal.*;
import javax.swing.tree.*;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.dnd.peer.*;
import java.awt.event.*;
import java.io.*;
import java.util.Collections;
import java.util.Vector;
import java.awt.Dimension;

public abstract class DnDJTree extends JTree
implements TreeSelectionListener, 
DragGestureListener, DropTargetListener,
DragSourceListener {


	/** Stores the selected node info */
	protected TreePath SelectedTreePath = null;
	protected DefaultMutableTreeNode SelectedNode = null;
	private DragSource dragSource = null;
	private DragSourceContext dragSourceContext = null;


	/** Constructor 
  @param root The root node of the tree
  @param parent Parent JFrame of the JTree */
	public DnDJTree(DefaultMutableTreeNode root) {

		super(root);
		addTreeSelectionListener(this);
		dragSource = DragSource.getDefaultDragSource() ;
		this.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) e
				.getPath().getLastPathComponent();
				
			}
		});
		DragGestureRecognizer dgr = 
			dragSource.createDefaultDragGestureRecognizer(
					this,                             //DragSource
					DnDConstants.ACTION_COPY_OR_MOVE, //specifies valid actions
					this                              //DragGestureListener
			);


		/* Eliminates right mouse clicks as valid actions - useful especially
		 * if you implement a JPopupMenu for the JTree
		 */
		dgr.setSourceActions(dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);

		/* First argument:  Component to associate the target with
		 * Second argument: DropTargetListener 
		 */
		DropTarget dropTarget = new DropTarget(this, this);

		//unnecessary, but gives FileManager look
		putClientProperty("JTree.lineStyle", "Angled");
		MetalTreeUI ui = (MetalTreeUI) getUI();
		this.revalidate();
		// Lastly, put the JTree into a JScrollPane.
		/*
		JScrollPane scrollpane = new JScrollPane();
		scrollpane.setSize(new Dimension(11, 15));
		scrollpane.getViewport().add(this);
		add(BorderLayout.CENTER, scrollpane);
		 */
	}



	/** Returns The selected node */
	public DefaultMutableTreeNode getSelectedNode() {
		return SelectedNode;
	}

	///////////////////////// Interface stuff ////////////////////


	/** DragGestureListener interface method */
	public void dragGestureRecognized(DragGestureEvent e) {

		DefaultMutableTreeNode dragNode = getSelectedNode();
		if (dragNode != null) {


			Transferable transferable = (Transferable) dragNode.getUserObject();

			Cursor cursor = DragSource.DefaultCopyNoDrop;
			int action = e.getDragAction();
			if (action == DnDConstants.ACTION_MOVE) 
				cursor = DragSource.DefaultMoveNoDrop;


			dragSource.startDrag(e, cursor, transferable, this);
		}
	}

	/** DragSourceListener interface method */
	public void dragDropEnd(DragSourceDropEvent dsde) {
	}

	/** DragSourceListener interface method */
	public void dragEnter(DragSourceDragEvent dsde) {
		/* ********************** CHANGED ********************** */
		/* ****************** END OF CHANGE ******************** */
	}

	/** DragSourceListener interface method */
	public void dragOver(DragSourceDragEvent dsde) {
		/* ********************** CHANGED ********************** */
		/* ****************** END OF CHANGE ******************** */
	}

	/** DragSourceListener interface method */
	public void dropActionChanged(DragSourceDragEvent dsde) {
	}

	/** DragSourceListener interface method */
	public void dragExit(DragSourceEvent dsde) {
	}

	public abstract DataFlavor getDataFlavor();



	/** DropTargetListener interface method - What we do when drag is released */
	public void drop(DropTargetDropEvent e) {
		try {
			Transferable tr = e.getTransferable();

			//flavor not supported, reject drop
			if (!tr.isDataFlavorSupported( this.getDataFlavor())) e.rejectDrop();

			//cast into appropriate data type
			CategoryInfo childInfo = 
				(CategoryInfo) tr.getTransferData( this.getDataFlavor());

			//get new parent node
			Point loc = e.getLocation();
			TreePath destinationPath = getPathForLocation(loc.x, loc.y);

			final String msg = testDropTarget(destinationPath, SelectedTreePath);
			if (msg != null) {
				e.rejectDrop();

				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						/* JOptionPane.showMessageDialog(
                 Parent, msg, "Error Dialog", JOptionPane.ERROR_MESSAGE
            );
						 */
					}
				});
				return;
			}


			DefaultMutableTreeNode newParent =
				(DefaultMutableTreeNode) destinationPath.getLastPathComponent();

			//get old parent node
			DefaultMutableTreeNode oldParent = (DefaultMutableTreeNode) getSelectedNode().getParent();

			int action = e.getDropAction();
			boolean copyAction = (action == DnDConstants.ACTION_COPY);

			//make new child node
			DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(childInfo);

			try { 
				if (!copyAction) oldParent.remove(getSelectedNode());
				newParent.add(newChild);

				if (copyAction) e.acceptDrop (DnDConstants.ACTION_COPY);
				else e.acceptDrop (DnDConstants.ACTION_MOVE);
			}
			catch (java.lang.IllegalStateException ils) {
				e.rejectDrop();
			}

			e.getDropTargetContext().dropComplete(true);

			//expand nodes appropriately - this probably isnt the best way...
			DefaultTreeModel model = (DefaultTreeModel) getModel();
			model.reload(oldParent);
			model.reload(newParent);
			TreePath parentPath = new TreePath(newParent.getPath());
			expandPath(parentPath);
		}
		catch (IOException io) { e.rejectDrop(); }
		catch (UnsupportedFlavorException ufe) {e.rejectDrop();}
	} //end of method


	/** DropTaregetListener interface method */
	public void dragEnter(DropTargetDragEvent e) {
	}

	/** DropTaregetListener interface method */
	public void dragExit(DropTargetEvent e) { 
	}

	/** DropTaregetListener interface method */
	public void dragOver(DropTargetDragEvent e) {
		/* ********************** CHANGED ********************** */
		//set cursor location. Needed in setCursor method
		Point cursorLocationBis = e.getLocation();
		TreePath destinationPath = 
			getPathForLocation(cursorLocationBis.x, cursorLocationBis.y);


		// if destination path is okay accept drop...
		if (testDropTarget(destinationPath, SelectedTreePath) == null){
			e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE ) ;
		}
		// ...otherwise reject drop
		else {
			e.rejectDrag() ;
		}
		/* ****************** END OF CHANGE ******************** */
	}

	/** DropTaregetListener interface method */
	public void dropActionChanged(DropTargetDragEvent e) {
	}


	/** TreeSelectionListener - sets selected node */
	public void valueChanged(TreeSelectionEvent evt) {
		SelectedTreePath = evt.getNewLeadSelectionPath();
		if (SelectedTreePath == null) {
			SelectedNode = null;
			return;
		}
		SelectedNode = 	(DefaultMutableTreeNode)SelectedTreePath.getLastPathComponent();
	}

	/** Convenience method to test whether drop location is valid
  @param destination The destination path 
  @param dropper The path for the node to be dropped
  @return null if no problems, otherwise an explanation
	 */
	public abstract  String testDropTarget(TreePath destination, TreePath dropper);




	public  static DefaultMutableTreeNode addNodes(DefaultMutableTreeNode root, String url){
		//TODO OVERRIDE
		return null;
	}

	public Dimension getMinimumSize() {
		return new Dimension(200, 400);
	}

	public Dimension getPreferredSize() {
		return new Dimension(200, 400);
	}








} //end of DnDJTree
